﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using HarmonyLib;
using MonoMod.RuntimeDetour;
using MonoMod.RuntimeDetour.Platforms;
using MonoMod.Utils;

namespace BepInEx.Preloader.RuntimeFixes
{
	internal static class XTermFix
	{
		public static void Apply()
		{
			if (PlatformHelper.Is(Platform.Windows))
				return;

			if (typeof(Console).Assembly.GetType("System.ConsoleDriver") == null)
			{
				// Mono version is too old, use our own TTY implementation instead
				return;
			}

			if (AccessTools.Method("System.TermInfoReader:DetermineVersion") != null)
			{
				// Fix has been applied officially
				return;
			}
			
			// Apparently on older Unity versions (4.x), using Process.Start can run Console..cctor
			// And since MonoMod's PlatformHelper (used by DetourHelper.Native) runs Process.Start to determine ARM/x86 platform,
			// this causes a crash owing to TermInfoReader running before it can be patched and fixed
			// Because Doorstop does not support ARM at the moment, we can get away with just forcing x86 detour platform.
			// TODO: Figure out a way to detect ARM on Unix without running Process.Start
			DetourHelper.Native = new DetourNativeX86Platform();

			var harmony = new HarmonyLib.Harmony("com.bepinex.xtermfix");

			harmony.Patch(AccessTools.Method("System.TermInfoReader:ReadHeader"),
				prefix: new HarmonyMethod(typeof(XTermFix), nameof(ReadHeaderPrefix)));

			harmony.Patch(AccessTools.Method("System.TermInfoReader:Get", new []{ AccessTools.TypeByName("System.TermInfoNumbers") }),
				transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoNumbersTranspiler)));

			harmony.Patch(AccessTools.Method("System.TermInfoReader:Get", new []{ AccessTools.TypeByName("System.TermInfoStrings") }),
				transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoStringsTranspiler)));

			harmony.Patch(AccessTools.Method("System.TermInfoReader:GetStringBytes", new []{ AccessTools.TypeByName("System.TermInfoStrings") }),
				transpiler: new HarmonyMethod(typeof(XTermFix), nameof(GetTermInfoStringsTranspiler)));

			DetourHelper.Native = null;
		}

		public static int intOffset;

		public static int GetInt32(byte[] buffer, int offset)
		{
			int b1 = buffer[offset];
			int b2 = buffer[offset + 1];
			int b3 = buffer[offset + 2];
			int b4 = buffer[offset + 3];

			return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
		}

		public static short GetInt16(byte[] buffer, int offset)
		{
			int b1 = buffer[offset];
			int b2 = buffer[offset + 1];

			return (short)(b1 | (b2 << 8));
		}

		public static int GetInteger(byte[] buffer, int offset)
		{
			return intOffset == 2
				? GetInt16(buffer, offset)
				: GetInt32(buffer, offset);
		}

		public static void DetermineVersion(short magic)
		{
			if (magic == 0x11a)
				intOffset = 2;
			else if (magic == 0x21e)
				intOffset = 4;
			else
				throw new Exception($"Unknown xterm header format: {magic}");
		}

		public static bool ReadHeaderPrefix(byte[] buffer, ref int position, ref short ___boolSize, ref short ___numSize, ref short ___strOffsets)
		{
			short magic = GetInt16(buffer, position);
			position += 2;
			DetermineVersion(magic);

			// nameSize = GetInt16(buffer, position);
			position += 2;
			___boolSize = GetInt16(buffer, position);
			position += 2;
			___numSize = GetInt16(buffer, position);
			position += 2;
			___strOffsets = GetInt16(buffer, position);
			position += 2;
			// strSize = GetInt16(buffer, position);
			position += 2;

			return false;
		}

		public static IEnumerable<CodeInstruction> GetTermInfoNumbersTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			// This implementation does not seem to have changed so I will be using indexes like the lazy fuck I am

			var list = instructions.ToList();

			list[31] = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(XTermFix), nameof(intOffset)));
			list[36] = new CodeInstruction(OpCodes.Nop);
			list[39] = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(XTermFix), nameof(GetInteger)));

			return list;
		}

		public static IEnumerable<CodeInstruction> GetTermInfoStringsTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			// This implementation does not seem to have changed so I will be using indexes like the lazy fuck I am

			var list = instructions.ToList();

			list[32] = new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(XTermFix), nameof(intOffset)));

			return list;
		}
	}
}